#!/usr/bin/env node

var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
var multer  = require('multer')
var os  = require('os')
var qrcode = require('./js/qrcode.js');
var upload = multer({ dest: '.' });
var _ = require('lodash');
var utils = require('./js/utils');

function startServer(port, param) {
    if (proxyRule) { // 代理
        const f = p => p[0] === '/' ? p : `/${p}`;
        const proxyList = proxyRule.split(',');
        for (const proxy of proxyList) {
            const list = proxy.split('->');
            console.log('=================', {
                target: list[1],
                changeOrigin: true,
                pathRewrite: { [f(list[0])]: '' },
            });
            app.use(f(list[0]),
                require("http-proxy-middleware").createProxyMiddleware({
                    target: list[1],
                    changeOrigin: true,
                    pathRewrite: {[f(list[0])]: ''},
                })
            );
        }
    }
    app.all("*", function(req, res, next) {
        res.header("Content-Type", "application/json;charset=utf-8");
        const list = decodeURI(req.url).split('/').filter(Boolean);
        const file = _.last(list);
        if (file) {
            const extname = path.extname(file);
            if (extname === '.tar') {
                const dirName = file.replace(new RegExp(extname+'$'), '');
                const dir = _.initial(list).concat(dirName).join('/');
                const info = fs.existsSync(dir) && fs.statSync(dir);
                if (info && info.isDirectory()) {
                    res.header('Content-Disposition',`attachment;filename=${dirName}.tar`);
                    return require('tar').c({ gzip: false }, [ dir ]).pipe(res);
                }
            } else if (extname === '.docx') {
                const dir = _.initial(list).concat(file).join('/');
                if (fs.existsSync(dir)) {
                    return require("mammoth").convertToHtml({ path: dir }) .then(result => {
                        res.setHeader('Content-type','text/html');
                        res.send(`<html><head><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><style>img {max-width:1000px;} body {background:#DDD;display:flex;justify-content:center} #main {width:1000px;height:100%;background:white;padding:20px;overflow:scroll;}</style></head><body><div id="main">${result.value}</div></body></html>`);
                    }).done();
                }
            } else if (extname === '.xlsx') {
                const dir = _.initial(list).concat(file).join('/');
                if (fs.existsSync(dir)) {
                    const getValues = (ws, rowNo) => _.map(ws.getRow(rowNo).values.slice(1), (o, index) => {
                        if (o && o.richText) {
                            return _.map(o.richText, m => m.text).join('');
                        }
                        if (o && o.result) {
                            return o.result;
                        }
                        if (o && o.error) {
                            return '';
                        }
                        return o;
                    }).map(o => o===undefined ? '' : o);
                    const getWidths = (ws, colCount) => {
                        const tw = 1000; // 总宽度
                        let widths = [];
                        for (let i=0; i<colCount; i++) {
                            widths.push(ws.columns[i].width||10);
                        }
                        const totalWidth = _.sum(widths);
                        widths = widths.map(o=>Math.round(o/totalWidth*tw)-4);
                        return widths;
                    };
                    return (async function() {
                        const Excel = require('exceljs');
                        const wb = new Excel.Workbook();
                        await wb.xlsx.readFile(dir);
                        let html = `<html>
                        <head>
                        <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
                        <style>
                        body {background:#DDD;display:flex;justify-content:center}
                        #main {width:1000px;height:100%;background:white;padding:20px;overflow:scroll;}
                        .title {font-size:24px;font-weight:600;margin-bottom:10px;margin-top:10px;}
                        table { border-collapse: collapse; border-spacing: 0; display: block; margin-bottom: 16px; margin-top: 0; overflow: auto; width: 100%; }
                        table tr { background-color: transparent; border-top: 1px solid #dfe2e5; height: 36px; }
                        table th, table td { border: 1px solid #dfe2e5; }
                        table th { background-color: #f6f8fa; color: inherit; font-weight: 600; }
                        table td { color: inherit; }
                        </style></head><body><div id="main">`;
                        wb.eachSheet(ws => {
                            html += `<div class="title">Sheet ${ws.name}</div><table>`
                            let count = 0;
                            ws.eachRow((row, rowNo)=>{
                                const values = getValues(ws, rowNo);
                                if (rowNo === 1) {
                                    count = values.length;
                                    const widths = getWidths(ws, count);
                                    html += `<thead><tr>${values.map((o, k)=>`<th style="text-align:center;min-width:${widths[k]}px;max-width:${widths[k]}px">${o}</th>`).join('')}</tr></thead><tbody>`;
                                } else {
                                    const oldCount = values.length;
                                    values.length = count;
                                    _.fill(values, '', oldCount, count);
                                    html += `<tr>${values.map(o=>`<td style="text-align:center;">${o}</td>`).join('')}</tr>`;
                                }
                            });
                        });
                        html += `</tbody></table></div></body></html>`;
                        res.setHeader('Content-type','text/html');
                        res.send(html);
                    })();
                }
            }
        }
        next();
    });
    var server = app.listen(port);
    server.on('listening', function () {
        if (viewFile) {
            const url = `http://${ip}:${port}/${viewFile}`;
            console.log(url);
            openChrome(url);
            return;
        }
        if (param === '-t' || param === '--text') {
            app.get('/clipboard', function(req, res){
                res.header('Content-Type', 'text/html;charset=utf-8');
                utils.paste((text)=>res.send(`<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=0"><meta charset="UTF-8"><div>${text}</div>`));
            });
            const url = `http://${ip}:${port}/clipboard`;
            console.log(url);
            qrcode.showCode(url);
        } else if (param === '-l' || param === '--html') {
            app.get('/clipboard', function(req, res){
                res.header('Content-Type', 'text/html;charset=utf-8');
                utils.paste((html)=>res.send(html));
            });
            const url = `http://${ip}:${port}/clipboard`;
            console.log(url);
            openChrome(url);
        } else if (typeof param !== 'undefined') {
            const url = `http://${ip}:${port}/${param}`;
            console.log(url);
            qrcode.showCode(url);
        } else {
            console.log(`http://${ip}:${port}`);
        }
        process.on('SIGINT', ()=>{
            if (typeof param !== 'undefined') {
                qrcode.resetScreen();
            }
             process.exit(0);
        })
    });
    server.on('error', function (err) {
        startServer(port+1, param);
    })
}

function startUploadServer(port, isText) {
    var server = app.listen(port);
    if (isText) {
        app.use(bodyParser.urlencoded());
        app.post('/sendText', function(req, res, next){
            var text = req.body.text;
            console.log(text);
            console.log('内容已经保存到剪切板');
            utils.copy(text);
        });
    } else {
        app.use(express.static(__dirname));
        app.post('/upload', upload.single('file'), function(req, res, next){
            const shell = require('shelljs');
            var file = req.file;
            if (file) {
                var dest = path.join(shell.pwd()+'', file.destination, file.originalname);
                shell.mv(file.path, dest);
                console.log('文件写入：', dest);
                var url = `http://${ip}:${port}/${file.originalname}`;
                return res.send(JSON.stringify({ url }));
            }
            res.send("{}");
        });
    }

    app.get('/*', function(req, res, next){
        var html = isText ? `
        <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=0">
        <form action="/sendText" method="post">
            <label>内容输入：</label>
            <textarea name="text" cols="30" rows="4"></textarea>
            <input type="submit" value="提交" />
        </form>
        ` : `
        <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=0">
        <style>
            body {
                display: flex;
                justify-content: center;
                align-items: center;
                flex-direction: column;
            }
            .dropbox {
                cursor: hand;
                margin-top: 40px;
                margin-bottom: 20px;
                width: 300px;
                height: 200px;
                border: 2px dashed #d9d9d9;
                display: flex;
                justify-content: center;
                align-items: center;
                color: grey;
            }
            #progress_container {
                width: 300px;
            }
            .title {
                color: grey;
                font-size: 18px;
            }
            .progress_item {
                width: 300px;
                height: 20px;
                border: 1px solid #d9d9d9;
                margin-bottom: 20px;
            }
            .progress_item .info {
                width: 300px;
                height: 20px;
                position: absolute;
                display: flex;
                justify-content: center;
                align-items: center;
            }
            .progress_item .bar {
                display: inline-block;
                width: 0;
                height: 20px;
                background-color: #64B587;
            }
        </style>
        <body>
            <h3>文件传输</h3>
            <input type="file" id="file" onchange="changeHandler(event)" multiple="multiple" style="visibility: hidden;" />
            <div class="dropbox" ondrop="dropHandler(event)" onclick="showSelectFile()">请将文件拖拽到此区域进行升级</div>
            <div id="progress_container"></div>
            <script>
                var noop = (e) => e.preventDefault();
                document.ondragenter = noop;
                document.ondragover = noop;
                document.ondragleave = noop;
                document.ondrop = noop;
                function dropHandler(e) {
                    e.preventDefault();
                    var fileList = e.dataTransfer.files;
                    for (const file of fileList) {
                        uploadFile(file);
                    }
                }
                function showSelectFile(){
                    document.getElementById('file').click();
                }
                function changeHandler(e){
                    var fileList = e.target.files;
                    for (const file of fileList) {
                        uploadFile(file);
                    }
                }
                function uploadFile(file){
                    var { info, bar } = createProgress(file.name);
                    var form = new FormData();
                    form.append("file", file);
                    var xhr = new XMLHttpRequest();
                    xhr.onerror = function(e) { info.style.color = "red"; info.innerHTML = "上传失败"; };
                    xhr.upload.onprogress = function(e) {
                        var loading = Math.round(e.loaded / e.total * 100);
                        info.innerHTML = loading + "%"
                        bar.style.width = loading * 3 + "px";
                    };
                    xhr.onreadystatechange = function() {
                        if (xhr.readyState === 4) {
                            const result = JSON.parse(xhr.responseText);
                            info.innerHTML = result.url ? '上传成功: url = '+ result.url : '上传失败';
                            info.style.color = "#413F43";
                        }
                    };
                    xhr.open("POST", "/upload", true);
                    xhr.send(form);
                }
                function createProgress(name) {
                    var container = document.getElementById('progress_container');
                    var title = document.createElement('div');
                    title.className = 'title';
                    title.appendChild(document.createTextNode(name));
                    container.appendChild(title);

                    var progressItem = document.createElement('div');
                    progressItem.className = 'progress_item';
                    var info = document.createElement('div');
                    info.className = 'info';
                    var bar = document.createElement('bar');
                    bar.className = 'bar';
                    progressItem.appendChild(info);
                    progressItem.appendChild(bar);
                    container.appendChild(progressItem);

                    return { info, bar };
                }
            </script>
        </body>
        `;
        res.send(html);
    });

    server.on('listening', function () {
        const url = `http://${ip}:${port}`;
        console.log(url);
        qrcode.showCode(url);
        process.on('SIGINT', ()=>{
            qrcode.resetScreen();
             process.exit(0);
        })
    });
    server.on('error', function (err) {
        startUploadServer(port+1, isText);
    })
}

function startImageServer(port, file, mode, offset, data) {
    if (+mode == mode) {
        offset = mode;
        mode = undefined;
    }
    var server = app.listen(port);
    app.get('/*', function(req, res, next){
        var html = `
        <style>
            body,html { margin: 0; padding: 0; background: black; }
            .hlineContainer { position: absolute; left: 0px; display: flex; flex-direction: row; background:rgba(0,0,0,0);}
            .hlineText { color: white; font-size: 8px; width: 20px; margin-left: 10px; }
            .hline { height: 1px; background:red; margin-left: 10px; flex: 1; }
            .vlineContainer { position: absolute; top: 0px; display: flex; flex-direction: column; background:rgba(0,0,0,0);}
            .vlineText { color: white; font-size: 8px; height: 20px; margin-top: 15px; display: flex; padding-right: 10px; }
            .vline { width: 1px; background:red; margin-top: 10px; flex: 1; }
            .point { width: 10px; height: 10px; border: 2px solid gold; position: absolute; }
        </style>
        <body>
            <img id="img" style="background: white; margin-left: 40px; margin-top: 40px; z-index: 100;"/>
            <script>
                var hmode = ${!mode||mode==='h'};
                var vmode = ${!mode||mode==='v'};
                var data = ${JSON.stringify(data||[])};
                var y = 0;
                var x = 0;
                var step = 10;
                var offset = 40;
                var ty = Math.floor((document.body.clientHeight+${offset||0}) / step);
                var tx = Math.floor((document.body.clientWidth+${offset||0}) / step);
                function createHL(index, step, offset) {
                    var div = document.createElement("div");
                    div.className = "hlineContainer";
                    div.style.top = offset + index * step;
                    div.style.height = step;
                    div.style.width = (tx+3) * step;
                    div.innerHTML = "<div class='hlineText'>"+(!(index%5) ? index * step : "" )+"</div><div class='hline' style='"+(!(index%5) ? "margin-left: 1px;background: green;" : "" )+"'></div>";
                    document.body.appendChild(div);
                }
                function createVL(index, step, offset) {
                    var div = document.createElement("div");
                    div.className = "vlineContainer";
                    div.style.left = offset + index * step;
                    div.style.width = step;
                    div.style.height = (ty+3) * step+5;
                    div.innerHTML = "<div class='vlineText'>"+(!(index%5) ? index * step : "" )+"</div><div class='vline' style='"+(!(index%5) ? "margin-top: 1px;background: green;" : "" )+"'></div>";
                    document.body.appendChild(div);
                }
                function createPoint(x, y) {
                    console.log(x, y);
                    var div = document.createElement("div");
                    div.className = "point";
                    div.style.left = x;
                    div.style.top = y;
                    document.body.appendChild(div);
                }
                while (hmode && y < ty) {
                    createHL(y, step, offset);
                    y++;
                }
                while (vmode && x < tx) {
                    createVL(x, step, offset);
                    x++;
                }
                for (const item of data) {
                    createPoint(item.x + offset, item.y + offset);
                }
                document.getElementById('img').src = "${file}";
            </script>
        <body>
        `;
        res.send(html);
    });

    server.on('listening', function () {
        const url = `http://${ip}:${port}`;
        console.log(url);
    });
    server.on('error', function (err) {
        startImageServer(port+1, file, mode, offset);
    })
}

function getHtmlInDir(dir) {
    var html = `<html><head>
    <title>${dir}的文件列表</title>
    <link rel="stylesheet" href="viewer/viewer.min.css" />
    <script type="text/javascript" src="viewer/viewer.min.js"></script>
    </head><body style="display:flex; flex-direction: row; flex-wrap: wrap;">`;
    fs.readdirSync(dir).forEach(function(file, index) {
        var fullname = path.join(dir, file);
        var info = fs.statSync(fullname);
        if(info.isDirectory()) {
            html += getHtmlInDir(fullname);
        } else if (!/.DS_Store/.test(file)) {
            html += `<div style="position:relative; width:144px; height: 144px; display: flex; justify-content: center; align-items: center; margin: 10px; padding:2px; background-color: #EBEBEB">`;
            if (['.png', '.jpg', '.gif', 'jpeg'].indexOf(path.extname(file)) !== -1) {
                html += `<img src="${fullname}" style="max-width: 140px; max-height: 140px;">`;
            } else {
                html += `<a href="${fullname}" style="max-width: 140px; max-height: 140px;">${file}</a>`;
            }
            html += `<span style="position: absolute; font-size: 10px; max-width: 140px; height: 12px; overflow: scroll; word-break: keep-all; top: 150px;">${fullname}</span></div>`;
        }
    });
    html += `</body>
    <script>
    var viewer = new Viewer(document.body, {
        toolbar: {
            prev: 1,
            zoomOut: 1,
            oneToOne: 1,
            play: 1,
            reset: 1,
            zoomIn: 1,
            next: 1,
        },
    })
    </script>
    </html>`;
    return html;
}

function startDirServer(port, dir) {
    var html = getHtmlInDir(dir);
    var server = app.listen(port);
    app.get('/list', function(req, res, next){
        res.send(html);
    });

    server.on('listening', function () {
        const url = `http://${ip}:${port}/list`;
        console.log(url);
        openChrome(url);
    });
    server.on('error', function (err) {
        startDirServer(port+1, dir);
    })
}

function createServerFile(filename, port = 4000, hasUpload) {
    if (port === 'true') {
        port = 4000;
        hasUpload = true;
    }
    code = 'var express = require("express");';
    code += '\nvar bodyParser = require("body-parser");';
    code += '\nvar shell = require("shelljs");';
    hasUpload && (code += '\nvar multer  = require("multer");');
    hasUpload && (code += '\nvar path = require("path");');
    hasUpload && (code += '\nvar upload = multer({ dest: "." });');
    code += '\nvar app = express();';
    code += '\n';
    code += '\napp.use(express.static(__dirname));';
    code += '\napp.use(bodyParser.urlencoded({ extended: false }));';
    code += '\napp.use(bodyParser.json());';
    code += '\napp.use(bodyParser.text());';
    code += '\n';
    code += '\napp.get("/test", (req, res)=>{';
    code += '\n    console.log(req.query);';
    code += '\n    res.send("hello");';
    code += '\n});';
    code += '\n';
    code += '\napp.post("/test", (req, res)=>{';
    code += '\n    console.log(req.body);';
    code += '\n    res.send("hello");';
    code += '\n});';
    code += '\n';
    hasUpload && (code += '\napp.post("/upload", upload.single("file"), function(req, res, next){');
    hasUpload && (code += '\n    var file = req.file;');
    hasUpload && (code += '\n    if (file) {');
    hasUpload && (code += '\n        var dest = path.join(shell.pwd()+"", file.destination, file.originalname);');
    hasUpload && (code += '\n        shell.mv(file.path, dest);');
    hasUpload && (code += '\n        url = file.originalname');
    hasUpload && (code += '\n        console.log("文件写入", dest);');
    hasUpload && (code += '\n        return res.send(JSON.stringify({ url }));');
    hasUpload && (code += '\n    }');
    hasUpload && (code += '\n    res.send("{}")');
    hasUpload && (code += '\n});');
    code += '\n';
    code += '\napp.listen('+port+', function() {';
    code += '\n    console.log("server listen on: http://localhost:'+port+'");';
    code += '\n});';
    fs.writeFileSync(filename, code);
}

function createHtmlFile(filename, url) {
    fs.writeFileSync(filename, `
        <html>
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        </head>
        <body>
        <script>
        function post(url, params, callback) {
            const request = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
            request.open('post', url, true);
            request.setRequestHeader("Content-type","application/json");
            request.send(JSON.stringify(params));
            request.onreadystatechange = function(){
                if(request.readyState == 4){
                    try{
                        callback(JSON.parse(request.responseText));
                    } catch (e) {
                        callback({});
                    };
                };
            };
        }
        post('${url}', {}, data=>{
            document.write(JSON.stringify(data, null, 2).replace(/\\n/g, '<br/>').replace(/\\s/g, '&nbsp;&nbsp;'));
        });
        </script>
        </body>
        </html>
        `
    );
}

function openChrome(url, isMobile) {
    if (os.platform() === 'win32') {
        require('child_process').exec(`start ${url}`);
    } else {
        require('child_process').exec(`osascript -e '
        tell application \"Google Chrome\"
            activate
            open location \"${url}\"
            ${!isMobile ? '' : `delay 0.5
            tell application \"System Events\"
                keystroke \"j\" using {option down, command down}
            end tell`}
        end tell
    '`);
    }
}

var ip = utils.getIPAdress();
console.log("ip:", ip);
var port = 4000;
var data;
var viewFile;
var proxyRule;
var app = express();
app.all('*', (req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    next();
});
var params = process.argv.slice(2);
var portIndex = params.indexOf('-p');
if (portIndex > -1 && params[portIndex + 1]) {
    port = params[portIndex + 1];
    params.splice(portIndex, 2);
}
var dataIndex = params.indexOf('-a');
if (dataIndex > -1 && params[dataIndex + 1]) {
    data = utils.eval(params[dataIndex + 1]).value;
    params.splice(dataIndex, 2);
}
var viewIndex = params.indexOf('-v');
if (viewIndex > -1 && params[viewIndex + 1]) {
    viewFile = params[viewIndex + 1];
    params.splice(viewIndex, 2);
}
var proxyIndex = params.indexOf('-r');
if (proxyIndex > -1 && params[proxyIndex + 1]) {
    proxyRule = params[proxyIndex + 1];
    params.splice(proxyIndex, 2);
}
var param = params[0];
var param1 = params[1];
var param2 = params[2];
var param3 = params[3];

if (!viewFile) {
    app.use(express.static('.'));
}
app.use(express.static(__dirname+'/static'));

if (param === '-h' || param === '--help') {
    console.log('');
    console.log('xserver opts');
    console.log('');
    console.log('   -h|--help: show help');
    console.log('   -c|--create [port [hasUpload]|html[url]]: create a app.js file for nodejs server or create html url');
    console.log('   -u|--upload [text]: upload files with multer or text to server');
    console.log('   -t|--text: show text from server\'s clipboard');
    console.log('   -l|--html: show html from clipboard');
    console.log('   -i|--image [name]: show iphone6 size for image');
    console.log('   -g|--grid [name] [v|h] [offset]: show grid for image');
    console.log('   -d|--dir [name]: show dir file list');
    console.log('   -v|--view [name]: start view file for server');
    console.log('   -r|--proxy [rule]: api->http://localhost:5000/apix,...');
    console.log('   -p|--port: set port for server');
    console.log('   -a|--data: data for a command');
    console.log('   file: show download qrcode for file');
    console.log('');
} else if (param === '-u' || param === '--upload') {
    startUploadServer(port, param1 === 'text');
} else if (param === '-c' || param === '--create') {
    if (param1 === 'html') {
        createHtmlFile('test.html', param2);
    } else {
        createServerFile('app.js', param1, param2);
    }
} else if (param === '-i' || param === '--image') {
    startServer(port);
    openChrome('http:'+ip+':'+port+'/'+param1, true);
} else if (param === '-d' || param === '--dir') {
    startDirServer(port, param1);
} else if (param === '-g' || param === '--grid') {
    startImageServer(port, param1, param2, param3, data);
    openChrome('http:'+ip+':'+port);
} else {
    startServer(port, param);
}
